package orfeu.engine;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.ShortBuffer;

import javax.microedition.khronos.opengles.GL10;
import javax.microedition.khronos.opengles.GL11;
import javax.microedition.khronos.opengles.GL11Ext;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.opengl.GLUtils;

/** describes a 2D textured quad to use for sprites **/
public class Sprite {
	// quad variables
	public float width, height;
	public int textureId;
	protected float[] vertices;
	protected short[] indices = {0, 1, 2, 0, 2, 3};
	protected float[] texcoords;
	
	// sprite properties
	protected Bitmap spriteImage;
	protected int noFrames;
	protected int currentFrame;
	protected long framePeriod;
	protected long frameTick;
	protected int resource;
	protected float speed;
	protected float interpolation;
	
	// buffers
	protected FloatBuffer vertexBuffer;
	protected ShortBuffer indexBuffer;
	protected FloatBuffer texcoordBuffer;
	
	// drawable properties
	public float px, py; //position
	protected float rx, ry; //rotation
	protected float sx, sy; //scale
	public orfeu.engine.AudioClip myaudioclip;
	
	
	//other properties
	protected boolean markedForDeletion; // used to see if we should delete a sprite from a list
	private static int[]demonTextures;

	
	
	public Sprite()
	{
		
	}
	
	/** constructor builds buffer objects **/
	public Sprite(float w, float h, int nf, long fp, int res) {
		//set delete properties
		markedForDeletion = false;
		
		// set quad properties
		width = w;
		height = h;
		
		// set sprite properties
		noFrames = nf;
		currentFrame = 0;
		framePeriod = fp;
		resource = res;
		
		// set drawable properties
		px = py = 0;
		rx = ry = 0;
		sx = sy = 1;
		
		// set vertices
		vertices = new float[] {-width / 2, -height / 2, 0.0f,
								width / 2, -height / 2, 0.0f,
								width / 2, height / 2, 0.0f,
								-width / 2, height / 2, 0.0f};
		
		// generate texture coordinates for the first frame
		texcoords = new float[] {(float)currentFrame * 1 / (float)noFrames, 0.0f,
								 (float)(currentFrame + 1) * 1 / (float)noFrames, 0.0f,
								 (float)(currentFrame + 1) * 1 / (float)noFrames, 1.0f,
								 (float)currentFrame * 1 / (float)noFrames, 1.0f};
		
		// build buffer objects
		ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length * 4); // 4 bytes per float
		vbb.order(ByteOrder.nativeOrder());
		vertexBuffer = vbb.asFloatBuffer();
		vertexBuffer.put(vertices);
		vertexBuffer.position(0);

		ByteBuffer ibb = ByteBuffer.allocateDirect(indices.length * 2); // 2 bytes per short
		ibb.order(ByteOrder.nativeOrder());
		indexBuffer = ibb.asShortBuffer();
		indexBuffer.put(indices);
		indexBuffer.position(0);
		
		setTexCoords(texcoords);	
	}
	
	/** setters **/
	public void setTexCoords(float[] texcoords) {
		ByteBuffer byteBuf = ByteBuffer.allocateDirect(texcoords.length * 4); //float is 4 bytes
		byteBuf.order(ByteOrder.nativeOrder());
		texcoordBuffer = byteBuf.asFloatBuffer();
		texcoordBuffer.put(texcoords);
		texcoordBuffer.position(0);
	}
	
	public void setSpeed(float speed) {
		this.speed = speed;
	}
	
	public void setInterpolation(float interp) { 
		interpolation = interp;
	}

	/** update functions **/
	public void update(long time) {
		tick(time);
		
		// move object
		px += speed * interpolation;
		
		// keep object in some bounds
		if (px > 125.0f)
			px = 125.0f;
	}
	
	public void tick(long time) {
		if (time - frameTick > framePeriod) {
			frameTick = time;
			currentFrame++;
			if (currentFrame > noFrames)
				currentFrame = 0;
			
			texcoords = new float[] {(float)currentFrame * 1 / (float)noFrames, 0.0f,
									 (float)(currentFrame + 1) * 1 / (float)noFrames, 0.0f,
									 (float)(currentFrame + 1) * 1 / (float)noFrames, 1.0f,
									 (float)currentFrame * 1 / (float)noFrames, 1.0f};
	
			setTexCoords(texcoords);
		}
	}
	
	/** getters **/
	public static int getTextureId(GL10 gl) {
		int[]temp = new int[1];
		gl.glGenTextures(1, temp, 0);
		return temp[0];
	}
	
	public static void createDemonTextures(GL10 gl, int nr)
	{
		Sprite.demonTextures = new int[nr];
		gl.glGenTextures(nr, demonTextures, 0);
	}
	
	public static int getDemonTextureId(int i) {
		return (Sprite.demonTextures == null)?null:Sprite.demonTextures[i];
	}
	
	public long getFramePeriod() {
		return framePeriod;
	}
	
	public long getFrameTick() {
		return frameTick;
	}
	
	public float getSpeed() {
		return speed;
	}
	
	/** function that loads a texture **/
	public void loadTexture(GL10 gl, Context context) {
		int id = getTextureId(gl);
		
		// flip the texture vertically
		Matrix flip = new Matrix();
		flip.postScale(1f, -1f);
		
		// tell BitmapFactory not to scale image
		BitmapFactory.Options opts = new BitmapFactory.Options();
		opts.inScaled = false;
		
		// load and flip the texture
		Bitmap temp = BitmapFactory.decodeResource(context.getResources(), resource, opts);
		spriteImage = Bitmap.createBitmap(temp, 0, 0, temp.getWidth(), temp.getHeight(), flip, true);
		temp.recycle();
		
		gl.glBindTexture(GL10.GL_TEXTURE_2D, id);
		
		gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_LINEAR_MIPMAP_NEAREST);
		gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR_MIPMAP_NEAREST);
		gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S, GL10.GL_REPEAT);
		gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T, GL10.GL_REPEAT);
		
		// generate and load mipmaps
		int[] crop = {0, spriteImage.getWidth(), spriteImage.getHeight(), -spriteImage.getHeight()};
		((GL11) gl).glTexParameteriv(GL10.GL_TEXTURE_2D, GL11Ext.GL_TEXTURE_CROP_RECT_OES, crop, 0);
		for(int level=0, height = spriteImage.getHeight(), width = spriteImage.getWidth(); true; level++) {
	        // push the bitmap onto the GPU:
	        GLUtils.texImage2D(GL10.GL_TEXTURE_2D, level, spriteImage, 0);
	        
	        // we need to stop when the texture is 1x1:
	        if(height==1 && width==1) break;
	        
	        // resize, and let's go again:
	        width >>= 1; height >>= 1;
	        if (width < 1)  
	        	width = 1;
	        if (height < 1) 
	        	height = 1;
	        
	        Bitmap bmp2 = Bitmap.createScaledBitmap(spriteImage, width, height, true);
	        spriteImage.recycle();
	        spriteImage = bmp2;
	    }
	    
		spriteImage.recycle();
	    
	    textureId = id;	
	}
	
	/** draw function to display the quad **/
	public void draw(GL10 gl) {
		gl.glPushMatrix();
		gl.glFrontFace(GL10.GL_CCW);
		gl.glEnable(GL10.GL_CULL_FACE);
		gl.glCullFace(GL10.GL_BACK);

		gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
		gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertexBuffer);
		gl.glEnable(GL10.GL_TEXTURE_2D);
        gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
        gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, texcoordBuffer);
        gl.glBindTexture(GL10.GL_TEXTURE_2D, textureId);
        
		gl.glTranslatef(px, py, 0);
		gl.glRotatef(rx, 1, 0, 0);
		gl.glRotatef(ry, 0, 1, 0);
		gl.glScalef(sx, sy, 1);
		
		gl.glDrawElements(GL10.GL_TRIANGLES, indices.length, GL10.GL_UNSIGNED_SHORT, indexBuffer);

		gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
		gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
		gl.glDisable(GL10.GL_CULL_FACE);
		gl.glPopMatrix();
	}
	
	public boolean collidesWith(Sprite s){
		//myaudioclip.play();
		return (((this.px - this.width/2) <= (s.px + s.width/2)) && 
				((this.px + this.width/2) >= (s.px - s.width/2)) && 
				((this.py - this.height/2) <= (s.py + s.height/2)) && 
				((this.py + this.height/2) >= (s.py - s.height/2)));
	}
	
	public boolean outOfBounds()
	{
		return false;
	}
	
	public boolean remove()
	{
		return markedForDeletion;
	}
	
	public void setRemove(){
		markedForDeletion = true;
	}
}
